require("SoldierTemplates");
require("Sprite");
require("CollisionManager");
require("Bullet");

Entity = {
    new = function(world, template, nation_type)
        local s = {}
        setmetatable(s, {__index = Entity})

        s.world = world
        s.nation = nation_type
        s.origin = template
        s.attributes = Templates.newTemplate(s.origin)

        s.dPosition = {x = 0, y = 0}
        s.position = {x = 0, y = 0}
        s.direction = {x = 0, y = 0}
		s.center = {x = 0, y = 0}
        s.dt = 0
        s.aux = {} -- tabela para dados auxiliares criados na AI

        s.sprite = Sprite.newSprite(s, s.origin.sprites.lenX, s.origin.sprites.lenY)
		--s.sprite:drawBoundingBox(true);
		
        s.enemy = nil

        s:setState(s.origin.initState)
		s:updateBoudingBox()

        return s
    end,

    setState = function(self, state)
        self.state = state
        self.sprite:setState(state)
    end,

    updateBoudingBox = function(self)
        self.attributes.box.x = self.position.x + self.origin.box.x
        self.attributes.box.y = self.position.y + self.origin.box.y
		self.center.x = self.position.x + (self.sprite.qxSize) / 2 
		self.center.y = self.position.y + (self.sprite.qySize) / 2 
    end,

    update = function(self, dt)
        self.dt = dt; -- Para utilização em métodos auxiliares ou na AI
        self:updateBoudingBox();

		

        self.attributes.aiAction[self.state](self);

        if self.attributes.hp <= 0 and self.state ~= States.DEAD then
            self:die();
        end

        if (not self.lowHPFactorActive) and (self.attributes.hp <= (self.origin.hp * 0.5)) then
            self.dt = self.dt * 0.5;
            --self.attributes.range = self.attributes.range * 0.7;
            self.attributes.rateOfFire = self.attributes.rateOfFire * 0.5;
            self.lowHPFactorActive = true;
        end

        self.sprite:update(dt);
    end,

    draw = function(self)
        self.sprite:draw(self.position.x, self.position.y);
    end,

    -- Coloca a entidade na posição especificada. Sem movimento.
    setPosition = function(self, x, y)
        self.position.x = x; self.dPosition.x = x;
        self.position.y = y; self.dPosition.y = y;
    end,

    -- Configura a direção da entidade e atualiza a sprites
    setDirection = function(self, x, y)
        self.sprite:flipAllStateSprites( x > 0, y < 0);
        self.direction.x = x;
        self.direction.y = y;
    end,

    -- Seleciona um inimigo na campo de visão
    firstEnemyNextToMe = function(self)
        for nation,units in pairs(self.world.soldiers) do
            for t,unit in ipairs(units) do
                 if self:reach(unit) and unit.nation ~= self.nation
                     and unit.state ~= States.DEAD then
                    return unit; -- Encontrou uma unidade próxima
				 end
            end
        end

        return nil; -- Não encontrou ninguem
    end,

    -- Seleciona o amigo mais próximo
    firstFriendNextToMe = function(self)
        local nextUnit = nil;
        for nation,units in pairs(self.world.soldiers) do
            for _,unit in ipairs(units) do
                 if self:reach(unit) and unit ~= self and
                   unit.nation == self.nation and
                   unit.state ~= States.DEAD then

                    if not nextUnit then
                        nextUnit = unit
                    else
                        if (math.abs(unit.position.x - self.position.x) <=
                           math.abs(nextUnit.position.x - self.position.x)) and
                           (math.abs(unit.position.y - self.position.y) <=
                           math.abs(nextUnit.position.y - self.position.y)) then
                            nextUnit = unit
                        end
                    end

                end
            end
        end

        return nextUnit;
    end,


    -- Verifica se o inimigo esta dentro da linhas de alcance
    reach = function(self, entityEnemy)
        ePos = entityEnemy.position;
        eBoxW = entityEnemy.attributes.box.w;
        eBoxH = entityEnemy.attributes.box.h;
        eDir = entityEnemy.direction;
        mPos = self.position;
        mAtt = self.attributes;
        mDir = self.direction;
		
		local mCenter = (mPos.x + mAtt.box.w / 2);
        visionLine = {sX = mCenter, eX = (mCenter + mAtt.range * mDir.x)};

		eCenter = ePos.x + eBoxW/2; 
        return (( eCenter >= visionLine.sX) and (eCenter <= visionLine.eX))
                or ((eCenter <= visionLine.sX) and (eCenter >= visionLine.eX));
    end,

    -- Entra no estado de ataque e prepara as sprites
    attack = function(self, entityEnemy)
        if entityEnemy then
            if self:reach(entityEnemy) and entityEnemy.state ~= States.DEAD then
                self:setState(States.ATTACK);
                self.enemy = entityEnemy;
            end
        end
    end,

    -- Entidade morta
    die = function(self)
        self:setState(States.DEAD);
        self.sprite:showHPBar(false)
        self.world:entityDied(self)
    end,

    -- Movimenta a entidade para o destino
    move = function(self)
        self:setState(States.WALK);

        -- quantidade de hp afeta o movimento da unidade
        local speedFactor = self.dt;
        local friend = self:firstFriendNextToMe();

        if friend then
            if CollisionManager.colliding(self, friend) then
                return;
            end
        end

        if (self.position.x < self.dPosition.x) then self.position.x = self.position.x + self.attributes.speed * speedFactor; end
        if (self.position.x > self.dPosition.x) then self.position.x = self.position.x - self.attributes.speed * speedFactor; end
        if (self.position.y < self.dPosition.y) then self.position.y = self.position.y + self.attributes.speed * speedFactor; end
        if (self.position.y > self.dPosition.y) then self.position.y = self.position.y - self.attributes.speed * speedFactor; end
    end,

    -- Seta a posição destino da entidade e coloca em estado de WALK
    moveTo = function(self, x, y)
        self.dPosition.x = x;
        self.dPosition.y = y;
        if (x ~= self.position.x) or (y ~= self.position.y) then
            self:setState(States.WALK);
        end
    end
}
